package net.pimpas.communication.serviceprovider.impl;

import java.util.ArrayList;
import java.util.List;

import net.pimpas.communication.channel.Channel;
import net.pimpas.communication.channel.impl.DefaultChannelImpl;
import net.pimpas.communication.exception.ChannelException;
import net.pimpas.communication.listener.ChannelListener;
import net.pimpas.communication.message.Message;
import net.pimpas.communication.serviceprovider.CommunicationServiceProvider;
import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.executor.ExecutorThreadType;
import net.pimpas.executor.serviceprovider.ExecutorServiceProvider;
import net.pimpas.executor.serviceprovider.impl.ExecutorServiceProviderFactoryImpl;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;

@ServiceProviderAnnotation(
		instanceType=ServiceProviderInstanceType.NORMAL,
		serviceProviderType=CommunicationServiceProvider.class)
public class CommunicationServiceProviderImpl implements CommunicationServiceProvider {
	
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(LoggingServiceProvider.class);
	private static final ExecutorServiceProvider executor = 
		ExecutorServiceProviderFactoryImpl.getExecutorServiceProvider("CommunicationExecutor");
	private Class<? extends Channel> channelImplClass;
	private List<Channel> channels;
	
	public CommunicationServiceProviderImpl() {
		this(DefaultChannelImpl.class);
	}
	
	public CommunicationServiceProviderImpl(Class<? extends Channel> clazz) {
		assert(clazz!=null);
		
		this.setChannelImplClass(clazz);
		this.setChannels(new ArrayList<Channel>());
	}

	public void config() throws ConfigurationException {
		logger.info("[Initializing CommunicationServiceProvider]");
		executor.configure(ExecutorThreadType.CACHED_THREAD_POOL);
	}

	public boolean isTerminated() {
		return executor.isShutdown();
	}
	
	private Channel hasChannel( String channelID ) {
		assert(channelID!=null);
		
		synchronized (this.getChannels()) {
			for(Channel channel: this.getChannels())
				if(channel.getId().equals(channelID)) return channel;
		}
		
		return null;
	}
	
	private Channel instanceChannel( String channelID, ExecutorServiceProvider executor ) {
		try {
			return this.getChannelImplClass().getConstructor(String.class,
					ExecutorServiceProvider.class).newInstance(channelID, executor);
		} catch(Exception e) {
			throw new RuntimeException("cannot instance channel of class "+this.getChannelImplClass().getName());
		}
	}

	public void closeChannel(String channelID) throws ChannelException {
		this.closeChannel(this.hasChannel(channelID));
	}

	public void closeChannel(Channel channel) throws ChannelException {
		String id = channel.getId();
		channel = this.hasChannel(id);
		if(channel == null) throw new ChannelException("[Channel]: Channel with ID ["+id+"] not found!");
		
		channel.close();
	}

	public Channel createChannel(String channelID, boolean forceCreate,
			ExecutorServiceProvider executor) throws ChannelException {
		logger.debug("[Channel]: Creating channel with ID ["+channelID+"]");
		Channel channel = this.hasChannel(channelID);
		if(channel != null) {
			logger.debug("[Channel]: Channel with ID ["+channelID+"] already exists!");
			if(forceCreate) throw new ChannelException("Channel "+channelID+" already exists!");
			
			return channel;
		}
		
		channel = this.instanceChannel(channelID, executor);
		synchronized(this.getChannels()) {
			this.getChannels().add(channel);
		}
		
		logger.debug("[Channel]: Channel with ID ["+channelID+"] created!");
		
		return channel;
	}

	public Channel createChannel(String channelID, boolean force) throws ChannelException {
		return this.createChannel(channelID, force, null);
	}

	public Channel getChannel(String channelID) throws ChannelException {
		return this.hasChannel(channelID);
	}

	public void subscribe(String channelID, ChannelListener listener) throws ChannelException {
		Channel channel = this.hasChannel(channelID);
		this.subscribe(channel, listener);
	}

	public void subscribe(Channel channel, ChannelListener listener) throws ChannelException {
		assert(channel!=null);
		assert(listener!=null);
		
		String id = channel.getId();
		channel = this.hasChannel(id);
		if(channel == null) throw new ChannelException("[Channel]: Channel with ID ["+id+"] not found!");
		
		channel.subscribe(listener);
	}

	public List<Message>unsubscribe(String channelID, ChannelListener listener) throws ChannelException {
		return this.unsubscribe(this.hasChannel(channelID), listener);
	}

	public List<Message> unsubscribe(Channel channel, ChannelListener listener) throws ChannelException {
		assert(channel!=null);
		assert(listener!=null);
		
		String id = channel.getId();
		channel = this.hasChannel(id);
		if(channel == null) throw new ChannelException("[Channel]: Channel with ID ["+id+"] not found!");
		
		return channel.unsubscribe(listener);
	}

	public Class<? extends Channel> getChannelImplClass() {
		return channelImplClass;
	}

	public void setChannelImplClass(Class<? extends Channel> channelImplClass) {
		this.channelImplClass = channelImplClass;
	}

	public List<Channel> getChannels() {
		return channels;
	}

	public void setChannels(List<Channel> channels) {
		this.channels = channels;
	}
}